집단 간 비교 : 심슨의 역설

plotnine
Author

강신성

Published

2023-10-14

심슨의 역설

여러 집단으로 나눠진 데이터들을 하나로 합쳐서 분석해버리면 어떻게 될까?

해당 포스트는 전북대학교 통계학과 최규빈 교수님의 강의내용을 토대로 재구성되었음을 알립니다.

1. 라이브러리 imports

import pandas as pd
import numpy as np
from plotnine import *

2. 필요한 코드 | 비교를 위한 시각화

A. geom_col()


- 예시1 : geom_col() 기본적인 막대 그래프

df = pd.DataFrame({'x':[0,1],'y':[40,60]})
df
x y
0 0 40
1 1 60
fig = ggplot(df)
bar = geom_col(aes(x = 'x', y = 'y'))   ## geom_bar()는 그냥 없다고 생각하자.

fig + bar

- 예시2 : \(x\)축이 범주형인 경우

df = pd.DataFrame({'sex':['male','female'],'score':[40,60]})
df
sex score
0 male 40
1 female 60
fig = ggplot(df)
bar = geom_col(aes(x = 'sex', y = 'score'))   ## 설명변수가 문자열이어도 산출해준다.

fig + bar

- 예시3 : fill = 'index'예시2에서 범주별 색깔로 구분하고 싶은 경우

df = pd.DataFrame({'sex':['male','female'],'score':[40,60]})
df
sex score
0 male 40
1 female 60
fig = ggplot(df)
bar = geom_col(aes(x = 'sex', y = 'score', fill = 'sex'))   ## color 옵션도 있으나, 이것은 바깥의 테두리 색상만 바꾼다.

fig + bar

- 예시4 : 예시3에서 scale_fill_manual()을 이용하여 색상 변경하기

df = pd.DataFrame({'sex':['male','female'],'score':[40,60]})
df
sex score
0 male 40
1 female 60
fig = ggplot(df)
bar = geom_col(aes(x = 'sex', y = 'score', fill = 'sex'))

fig + bar + scale_fill_manual(['red','blue'])

색상 입력에는 이름이 정해진 색상들 뿐만 아니라 plt.plot(color = option)에서의 옵션과 같이 이미 설정된 C0, C1… 또는 hex code도 입력이 가능하다.

### B. facet_warp() | 한 면을 감싸다.

- 예시1 : facet_warp()를 이용한 면분할 – 반별로 면분할

df = pd.DataFrame({'sex':['male','female','male','female'],'score':[40,60,50,20],'class':['A','A','B','B']})
df
sex score class
0 male 40 A
1 female 60 A
2 male 50 B
3 female 20 B
ggplot(df) + geom_col(aes(x='sex',y='score',fill='sex')) + scale_fill_manual(['red','blue']) + facet_wrap('class')

class별로 그래프의 면을 분할해서 표시

ggplot(df) + geom_col(aes(x = 'sex', y = 'score', fill = 'sex')) + scale_fill_manual(['red','blue'])  ## 지정해주지 않을 경우 기본적으로 합산하여 지정된다.

- 예시2 : 성별로 면분할

df = pd.DataFrame({'sex':['male','female','male','female'],'score':[40,60,50,20],'class':['A','A','B','B']})
df
sex score class
0 male 40 A
1 female 60 A
2 male 50 B
3 female 20 B
ggplot(df) + geom_col(aes(x='class',y='score',fill='sex')) + facet_wrap('sex')

이 경우 'sex'color를 구분했으므로 면마다 다른 색의 그래프만이 나온다.

4. 심슨의 역설

- 버클리 대학교의 입학 데이터에서 gender bias가 존재한다는 주장이 있었다.

df=pd.read_csv("https://raw.githubusercontent.com/guebin/DV2022/master/posts/Simpson.csv",index_col=0,header=[0,1])\
.stack().stack().reset_index()\
.rename({'level_0':'department','level_1':'result','level_2':'gender',0:'count'},axis=1)
df
department result gender count
0 A fail female 19
1 A fail male 314
2 A pass female 89
3 A pass male 511
4 B fail female 7
5 B fail male 208
6 B pass female 18
7 B pass male 352
8 C fail female 391
9 C fail male 204
10 C pass female 202
11 C pass male 121
12 D fail female 244
13 D fail male 279
14 D pass female 131
15 D pass male 138
16 E fail female 299
17 E fail male 137
18 E pass female 94
19 E pass male 54
20 F fail female 103
21 F fail male 149
22 F pass female 238
23 F pass male 224

A. 시각화 1 : 전체 합격률 시각화 – pandas 초보


- 단순무식하게 query만 이용해서 그룹화

df.query('gender == "female" and result == "pass"')['count'].sum()
772

여성 지원자 중 합격한 사람의 수

df.query('gender == "female"')['count'].sum()
1835

총 여성 지원자 수

(df.query('gender == "female" and result == "pass"')['count'].sum()/df.query('gender == "female"')['count'].sum(),
 df.query('gender == "male" and result == "pass"')['count'].sum()/df.query('gender == "male"')['count'].sum())
(0.420708446866485, 0.5202526941657376)
tidydata_ = pd.DataFrame({'female' : [df.query('gender == "female" and result == "pass"')['count'].sum()/df.query('gender == "female"')['count'].sum()],
                          'male' : [df.query('gender == "male" and result == "pass"')['count'].sum()/df.query('gender == "male"')['count'].sum()]})

tidydata_
female male
0 0.420708 0.520253

이렇게 하면 시각화 못해요…

tidydata = pd.DataFrame({'sex' : ['male', 'female'],
                         'rate' : [df.query('gender == "female" and result == "pass"')['count'].sum()/df.query('gender == "female"')['count'].sum(),
                                   df.query('gender == "male" and result == "pass"')['count'].sum()/df.query('gender == "male"')['count'].sum()]})

tidydata
sex rate
0 male 0.420708
1 female 0.520253

### B. 시각화 1 : 전체 합격률 시각화 – pandas 고수

df.pivot_table(index = row, columns = col, valuse = value_col, aggfunc = func)

- 피벗 테이블을 만든다. (두 범주형 자료들을 나누는 것)

df.pivot_table(index='gender',columns='result',values='count',aggfunc=sum)
result fail pass
gender
female 1063 772
male 1291 1400
df.pivot_table(index='gender',columns='result',values='count')    ## 집계함수의 디폴트 값이 mean이다
result fail pass
gender
female 177.166667 128.666667
male 215.166667 233.333333

이 상태에서 reset_index()를 통해 자료를 쉽게 정리할 수 있다.

df.pivot_table(index='gender',columns='result',values='count',aggfunc=sum)\
.assign(rate = lambda _df : _df['pass']/(_df['fail'] + _df['pass']))
## 비율까지 추가한 모습, lambda _df : _df를 통해 현재 데이터프레임까지 호출한 모습
result fail pass rate
gender
female 1063 772 0.420708
male 1291 1400 0.520253
df.pivot_table(index='gender',columns='result',values='count',aggfunc=sum)\
.assign(rate = lambda _df : _df['pass']/(_df['fail'] + _df['pass'])).reset_index()
result gender fail pass rate
0 female 1063 772 0.420708
1 male 1291 1400 0.520253

- 이제 가공된 데이터를 tidydata라고 하여 그래프를 그려보자.

tidydata = df.pivot_table(index='gender',columns='result',values='count',aggfunc=sum).assign(rate = lambda _df : _df['pass']/(_df['fail'] + _df['pass'])).reset_index()

fig = ggplot(tidydata)
bar = geom_col(aes(x='gender',fill='gender',y='rate'))
fig+bar

이 그래프를 보고 나온 주장 : 여성이 남성에 비해 합격률이 낮으니까 차별이 있다!!

- 반박

df.pivot_table(index=['department'],columns=['result','gender'],values='count',aggfunc=sum).stack()
##df.pivot_table(index = ['department', 'gender'], columns = 'result', values = 'count', aggfunc = sum)
## 위의 두 코드는 완전히 똑같은 동작을 한다. 아마도...
result fail pass
department gender
A female 19 89
male 314 511
B female 7 18
male 208 352
C female 391 202
male 204 121
D female 244 131
male 279 138
E female 299 94
male 137 54
F female 103 238
male 149 224
temp.pass  ## pass 자체가 파이썬에서 특수하게 사용되는 조건어같은 거여서 안된다.
SyntaxError: invalid syntax (2928908933.py, line 1)
temp = df.pivot_table(index=['department'],columns=['result','gender'],values='count',aggfunc=sum).stack()
temp['fail'] + temp['pass']
department  gender
A           female    108
            male      825
B           female     25
            male      560
C           female    593
            male      325
D           female    375
            male      417
E           female    393
            male      191
F           female    341
            male      373
dtype: int64
df.pivot_table(index=['department'],columns=['result','gender'],values='count',aggfunc=sum).stack()\
.assign(rate = lambda _df: _df['pass']/(_df.fail+_df['pass']))
##df.pivot_table(index = ['gender', 'department'], columns = 'result', values = 'count', aggfunc = sum).reset_index().assign(rate = lambda _df: _df['pass']/(_df.fail+_df['pass']))
result fail pass rate
department gender
A female 19 89 0.824074
male 314 511 0.619394
B female 7 18 0.720000
male 208 352 0.628571
C female 391 202 0.340641
male 204 121 0.372308
D female 244 131 0.349333
male 279 138 0.330935
E female 299 94 0.239186
male 137 54 0.282723
F female 103 238 0.697947
male 149 224 0.600536

- tidydata 완성

df.pivot_table(index=['department'],columns=['result','gender'],values='count',aggfunc=sum).stack()\
.assign(rate = lambda _df: _df['pass']/(_df.fail+_df['pass'])).reset_index().drop(['fail', 'pass'], axis = 1)

tidydata = _
tidydata
result department gender rate
0 A female 0.824074
1 A male 0.619394
2 B female 0.720000
3 B male 0.628571
4 C female 0.340641
5 C male 0.372308
6 D female 0.349333
7 D male 0.330935
8 E female 0.239186
9 E male 0.282723
10 F female 0.697947
11 F male 0.600536
fig = ggplot(tidydata)
bar = geom_col(aes(x = 'gender', y = 'rate', fill = 'gender'))

fig + bar + scale_fill_manual(['red', 'blue']) + facet_wrap(['department'])

C와 E를 제외하고는 오히려 여성의 합격 비율이 더 높은 것을 알 수 있다.

D. 해석


- 시각화 1 : 남자의 합격률이 더 높다 -> 성차별이 있어보인다(?)

- 시각화 2 : 학과별로 살펴보니 오히려 A, B, F, D의 경우 여성의 합격률이 높다.

- 교재에서 설명한 이유 : 여성이 합격률이 낮은 학과에만 많이 지원하였기 때문.

df.pivot_table(index='department', columns='gender', values='count',aggfunc='sum')\
.stack().reset_index().rename({0:'count'},axis=1)
department gender count
0 A female 108
1 A male 825
2 B female 25
3 B male 560
4 C female 593
5 C male 325
6 D female 375
7 D male 417
8 E female 393
9 E male 191
10 F female 341
11 F male 373
tidydata = df.pivot_table(index='department', columns='gender', values='count',aggfunc='sum')\
.stack().reset_index().rename({0:'count'},axis=1)

 
fig = ggplot(tidydata) 
col = geom_col(aes(x='department',y='count',fill='gender'),position='dodge')
fig+col

A, B학과가 신체적 역량을 많이 요구로 하거나 하는 학과일 경우 아무래도 기피하겠지…

은닉변수에 의해 왜곡이 될 수 있다